﻿
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Database;
using System.Xml;
using MacomberMapSystem.Common.Internals;
using System.IO;
using System.Drawing.Imaging;
using MacomberMapSystem.Common.Exports;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.CIM;
using System.Threading;
using MacomberMapSystem.Common.Processing;
#if DotNetOracle
    using System.Data.OracleClient;
#else
    using Oracle.DataAccess.Client;
#endif
using System.Windows.Forms.DataVisualization.Charting;
using MacomberMapSystem.Common.Integration.Blackstart;

namespace MacomberMapSystem.Common.User_Interfaces.One_Lines
{
    /// <summary>
    /// This class offers a preview of a one-line
    /// </summary>
    public partial class ctlOneLinePreview : UserControl
    {
        #region Variable declarations
        /// <summary>The active one-line</summary>
        public MM_Database_OneLine ActiveOneLine;

        /// <summary>The model matched up with this one-line</summary>
        public MM_Database_Model ActiveModel;

        /// <summary>Our repository</summary>
        public MM_Repository Repository;

        /// <summary>Our collection of elements w/ nodes</summary>
        public Dictionary<CIM_RdfID, TreeNode> Elements = new Dictionary<CIM_RdfID, TreeNode>();

        /// <summary>Our collection of elements w/ nodes</summary>
        public Dictionary<XmlElement, Rectangle> ElementXml = new Dictionary<XmlElement,Rectangle>();


        /// <summary>Whether element drag/drop functionality is offered</summary>
        public bool EnableDragDrop = false;

        /// <summary>The point at which the mouse is pushed down</summary>
        private Point MouseDownPoint = Point.Empty;

        /// <summary>The selected rectangle at the mouse down point</summary>
        private XmlElement SelectedElement;

        /// <summary>A delegate for handling an element click</summary>
        /// <param name="xElem"></param>
        public delegate void ElementClickDelegate(XmlElement xElem);

        /// <summary>Our event handler for an element click</summary>
        public event ElementClickDelegate ElementClicked;

        /// <summary>Our collection of blackstart corridor elements</summary>
        public MM_Blackstart_Corridor_Element[] BlackstartElements = null;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new preview form
        /// </summary>
        public ctlOneLinePreview()
        {
            InitializeComponent();
        }
        #endregion

        /// <summary>
        /// When the image is being drawn, highlight the needed components
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void picImage_Paint(object sender, PaintEventArgs e)
        {
            //First, draw all sub-elements
            if (tvOneLine.SelectedNode != null && tvOneLine.SelectedNode.Parent != null)
                DrawElement(tvOneLine.SelectedNode, e.Graphics, true);

            //Now, draw any blackstart elements
            TreeNode FoundNode;
            if (BlackstartElements != null)
                using (StringFormat sF = new StringFormat() { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Near })
                using (Font DrawFont = new Font("Arial", 14, FontStyle.Bold))
                using (Pen DrawPen = new Pen(Color.White, 3))
                {
                    Dictionary<XmlElement, String> OutElems = new Dictionary<XmlElement, string>();
                    for (int a = 0; a < BlackstartElements.Length; a++)
                        if (BlackstartElements[a].xConfig.HasAttribute("rdfID") && Elements.TryGetValue(new CIM_RdfID(BlackstartElements[a].xConfig.Attributes["rdfID"].Value), out FoundNode))
                        {
                            KeyValuePair<XmlElement, Rectangle> kvp = (KeyValuePair<XmlElement, Rectangle>)FoundNode.Tag;
                            if (OutElems.ContainsKey(kvp.Key))
                                OutElems[kvp.Key] += "\n" + (a+1).ToString();
                            else
                                OutElems.Add(kvp.Key, (a+1).ToString());
                        }

                    foreach (KeyValuePair<XmlElement, String> kvp in OutElems)
                    {
                        Size StringSize = Size.Ceiling(e.Graphics.MeasureString(kvp.Value, DrawFont));
                        String Orientation = kvp.Key.HasAttribute("Orientation") ? kvp.Key.Attributes["Orientation"].Value : "Vertical";
                        Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(kvp.Key.Attributes["Bounds"].Value,null);
                        if (Orientation == "Vertical" || Orientation == "Up" || Orientation == "Down")
                        {
                            e.Graphics.DrawString(kvp.Value, DrawFont, Brushes.White, Bounds.Location, sF);
                            e.Graphics.DrawRectangle(DrawPen, Bounds.Left - StringSize.Width, Bounds.Top - 2, Bounds.Width + StringSize.Width, Math.Max(Bounds.Height, StringSize.Height) + 4);
                        }
                        else
                        {
                            e.Graphics.DrawString(kvp.Value, DrawFont, Brushes.White, Bounds.Right, Bounds.Top - StringSize.Height, sF);
                            e.Graphics.DrawRectangle(DrawPen, Math.Min(Bounds.Left, Bounds.Right - StringSize.Width) - 2, Bounds.Top - StringSize.Height - 2, Math.Max(Bounds.Width, StringSize.Width) + 4, Bounds.Height + StringSize.Height + 4);
                        }
                    }
                }
        }

        /// <summary>
        /// Draw an element with the specified pen
        /// </summary>
        /// <param name="Node">The tree node to be highlighted</param>
        /// <param name="g">The graphics connector</param>
        /// <param name="Primary">Whether the element is the primary element</param>
        private void DrawElement(TreeNode Node, Graphics g, bool Primary)
        {
            //First, draw all sub-elements
            foreach (TreeNode SubElement in Node.Nodes)
                DrawElement(SubElement, g, false);

            //Now, draw our requested element            
            if (Node.Tag is KeyValuePair<XmlElement, Rectangle>)
            {
                KeyValuePair<XmlElement, Rectangle> FoundElem = (KeyValuePair<XmlElement, Rectangle>)Node.Tag;
               
                if (Primary)
                    using (Pen HighlightPen = new Pen(Color.Red, 3f))
                        g.DrawRectangle(HighlightPen, FoundElem.Value.Left - 2, FoundElem.Value.Top - 2, FoundElem.Value.Width + 4, FoundElem.Value.Height + 4);
                //g.DrawRectangle(HighlightPen, FoundElem.Value.Left - 3, FoundElem.Value.Top - 3, FoundElem.Value.Width + 6, FoundElem.Value.Height + 6);
                else
                    using (Pen HighlightPen = new Pen(Color.DarkRed, 2f))
                        g.DrawRectangle(HighlightPen, FoundElem.Value.Left - 2, FoundElem.Value.Top - 2, FoundElem.Value.Width + 4, FoundElem.Value.Height + 4);
                if (FoundElem.Key.HasAttribute("LabelBounds") && !FoundElem.Key.Attributes["LabelBounds"].Value.StartsWith("0,0,"))
                    g.DrawRectangle(Pens.CadetBlue, MM_Serializable<Rectangle>.ConvertObject(FoundElem.Key.Attributes["LabelBounds"].Value, null));
            }
        }

        /// <summary>
        /// After a node is selected, highlight it
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvOneLine_AfterSelect(object sender, TreeViewEventArgs e)
        {
            picImage.Refresh();
            if (ElementClicked != null && e.Node != null && e.Node.Tag is KeyValuePair<XmlElement, Rectangle>)
                ElementClicked(((KeyValuePair<XmlElement, Rectangle>)e.Node.Tag).Key);
        }

        /// <summary>
        /// Assign the active one-line
        /// </summary>
        /// <param name="OneLine">The active one-line</param>
        /// <param name="PreviewImage">The preview image</param>
        /// <param name="Model">The model</param>
        /// <param name="Repository">The repository</param>
        public void AssignOneLine(MM_Database_OneLine OneLine, Image PreviewImage, MM_Database_Model Model, MM_Repository Repository)
        {
            this.ActiveModel = Model;
            this.Repository = Repository;
            this.ActiveOneLine = OneLine;            
            pnlPicture.AutoScrollPosition = Point.Empty;
            //picImage.Image = MM_Image_Exporter.OneLineImage(ActiveOneLine, ActiveModel);

            picImage.Image = PreviewImage;
            
            tvOneLine.Nodes.Clear();
            tvOneLine.BeginUpdate();
            Elements.Clear();
            ElementXml.Clear();
            if (OneLine != null)
            {
                TreeNodeCollection RootNode = (tvOneLine.Nodes.Add(" " + String.Join(" / ", OneLine.Names))).Nodes;

                //Propigate our elements

                foreach (XmlElement xParent in OneLine.Document.DocumentElement.ChildNodes)
                    if (xParent.Name == "Elements" || xParent.Name == "Nodes")
                        foreach (XmlElement xElem in xParent.ChildNodes)
                        {
                            String KVLevel;
                            if (xElem.HasAttribute("BaseElement.KVLevel"))
                                KVLevel = xElem.Attributes["BaseElement.KVLevel"].Value;
                            else
                                KVLevel = "Transformer";

                            TreeNode KVNode = RootNode[KVLevel];
                            if (KVNode == null)
                                KVNode = RootNode.Add(KVLevel, KVLevel);


                            TreeNode ElemType = KVNode.Nodes[xElem.Name];
                            if (ElemType == null)
                                ElemType = KVNode.Nodes.Add(xElem.Name, xElem.Name);

                            TreeNode ElemNode;
                            if (xElem.HasAttribute("BaseElement.LongName") && !String.IsNullOrEmpty(xElem.Attributes["BaseElement.LongName"].Value))
                                if (xElem.Attributes["BaseElement.LongName"].Value == xElem.Attributes["BaseElement.Name"].Value)
                                    ElemNode = ElemType.Nodes.Add(xElem.Attributes["BaseElement.TEID"].Value, xElem.Attributes["BaseElement.LongName"].Value);
                                else
                                    ElemNode = ElemType.Nodes.Add(xElem.Attributes["BaseElement.TEID"].Value, xElem.Attributes["BaseElement.LongName"].Value + " (" + xElem.Attributes["BaseElement.Name"].Value + ")");
                            else
                                ElemNode = ElemType.Nodes.Add(xElem.Attributes["BaseElement.TEID"].Value, xElem.Attributes["BaseElement.Name"].Value);
                            ElemNode.Tag = new KeyValuePair<XmlElement, Rectangle>(xElem, MM_Type_Converter.GetRectangle(xElem.Attributes["Bounds"].Value));
                            Elements.Add(new CIM_RdfID(xElem.Attributes["rdfID"].Value), ElemNode);
                            ElementXml.Add(xElem, MM_Type_Converter.GetRectangle(xElem.Attributes["Bounds"].Value));

                            //Now, create our sub-elements for this one
                            foreach (XmlElement xCh in xElem.ChildNodes)
                            {
                                TreeNode SubNode = null;
                                if (xCh.HasAttribute("Bounds"))
                                    (SubNode = ElemNode.Nodes.Add(xCh.Name)).Tag = new KeyValuePair<XmlElement, Rectangle>(xCh, MM_Type_Converter.GetRectangle(xCh.Attributes["Bounds"].Value));
                                else if (xCh.HasAttribute("TEID"))
                                {
                                    XmlNode FoundNode = OneLine.Document.SelectSingleNode("//*[@BaseElement.TEID='" + xCh.Attributes["TEID"].Value + "']");
                                    if (FoundNode.Attributes["Bounds"] != null)
                                        (SubNode = ElemNode.Nodes.Add(FoundNode.Name + " " + FoundNode.Attributes["BaseElement.Name"].Value)).Tag = new KeyValuePair<XmlElement, Rectangle>(FoundNode as XmlElement, MM_Type_Converter.GetRectangle(FoundNode.Attributes["Bounds"].Value)); ;
                                }
                                foreach (XmlElement xCh2 in xCh.ChildNodes)
                                    if (xCh2.HasAttribute("Bounds"))
                                        SubNode.Nodes.Add(xCh2.Name).Tag = new KeyValuePair<XmlElement, Rectangle>(xCh2, MM_Type_Converter.GetRectangle(xCh2.Attributes["Bounds"].Value)); ;

                            }
                            ElemNode.EnsureVisible();
                        }
                //For Nitika: If we have a model opened, also pull in the connectivity node groups associated with a substation one-line
                if (Model.State == MM_Database_Model.enumModelState.Completed && OneLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.Substation)
                {
                    CIM_Element Substation = Model.FromRdfID(OneLine.rdfID);
                    foreach (CIM_Element NodeGroup in Substation.ElementsOfType("etx:ConnectivityNodeGroup"))
                    {
                        TreeNode KVLevel = RootNode[NodeGroup.VoltageLevel.Name];
                        if (KVLevel == null)
                            break;
                        TreeNode CNG = KVLevel.Nodes["ConnectivityNodeGroup"];
                        if (CNG == null)
                            CNG = KVLevel.Nodes.Add("ConnectivityNodeGroup","ConnectivityNodeGroup");
                        TreeNode CNGNode = CNG.Nodes.Add(NodeGroup.TEID, NodeGroup.Name);
                        foreach (CIM_Link Node in NodeGroup.Links)
                            if (Node.TypeName == "cim:ConnectivityNode")
                                CNGNode.Nodes.Add(Node.Element.TEID, Node.Element.Name).Tag = Elements[Node.rdfID].Tag;
                        CNGNode.EnsureVisible();
                    }

                }
                tvOneLine.Sort();
            }

            

            tvOneLine.EndUpdate();
            tvOneLine.Focus();
            tvOneLine.SelectedNode = null;
        }

        /// <summary>
        /// Offer the right-click menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvOneLine_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button == MouseButtons.Right && e.Node != null && e.Node.Tag is KeyValuePair<XmlElement, Rectangle>)
            {
                cmsMenu.Items.Clear();
                XmlElement BaseElement = ((KeyValuePair<XmlElement, Rectangle>)e.Node.Tag).Key;
                cmsMenu.Items.Add(BaseElement.Name);
                foreach (XmlAttribute xAttr in BaseElement.Attributes)
                {
                    String[] splStr = xAttr.Name.Split('.');
                    ToolStripItemCollection c = cmsMenu.Items;
                    for (int a = 0; a < splStr.Length - 1; a++)
                        if (c.ContainsKey(splStr[a]))
                            c = (c[splStr[a]] as ToolStripMenuItem).DropDownItems;
                        else
                        {
                            ToolStripMenuItem NewItem = c.Add(splStr[a]) as ToolStripMenuItem;
                            NewItem.Name = splStr[a];
                            c = NewItem.DropDownItems;
                        }
                    c.Add(splStr[splStr.Length - 1] + ": " + xAttr.Value);
                }

                if (ActiveModel.State == MM_Database_Model.enumModelState.Completed && (BaseElement.HasAttribute("rdfID") || BaseElement.HasAttribute("BaseElement.TEID")))
                {
                    CIM_Element FoundElem=null;
                    if (BaseElement.HasAttribute("rdfID"))
                        FoundElem = ActiveModel.FromRdfID(BaseElement.Attributes["rdfID"].Value);
                    if (FoundElem == null)
                        FoundElem = ActiveModel.FromRdfID(BaseElement.Attributes["BaseElement.TEID"].Value);

                    if (FoundElem != null)
                    {
                        cmsMenu.Items.Add("-");
                        ToolStripMenuItem tmi = cmsMenu.Items.Add("Element (" + ActiveModel.FullClass + ")") as ToolStripMenuItem;
                        foreach (KeyValuePair<String, String> kvp in FoundElem.AttributeList)
                        {
                            ToolStripMenuItem tmd = tmi.DropDownItems[kvp.Key.Split(':', '.')[1]] as ToolStripMenuItem;
                            if (tmd == null)
                                (tmd = tmi.DropDownItems.Add(kvp.Key.Split(':', '.')[1]) as ToolStripMenuItem).Name = kvp.Key.Split(':', '.')[1];
                            tmd.DropDownItems.Add(kvp.Key.Split(':', '.')[2] + ": " + kvp.Value);
                        }
                        tmi.DropDownItems.Add("-");
                        FoundElem.WriteMenuElement(tmi.DropDownItems, cmsMenu);
                    }
                }

                cmsMenu.Show(sender as Control, e.Location);
            }

        }



       

        /// <summary>
        /// Validate the current one-line against a particular model
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuModels_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            cmsMenu.Close();
            MM_Database_Model Model = e.ClickedItem.Tag as MM_Database_Model;
            //First, make sure our selected model is opened
            if (Model.State == MM_Database_Model.enumModelState.Unopened)
                if (MessageBox.Show("Open model " + Model.Name + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                    return;
                else
                    Model.InitiateLoading();
            while (Model.State != MM_Database_Model.enumModelState.Completed)
                if (Model.State == MM_Database_Model.enumModelState.Error)
                    return;
                else
                    Thread.Sleep(500);
            Dictionary<String, List<string>> ValidationSummaries = new Dictionary<string, List<string>>();
            CIM_Element BaseElement = Model.FromTEID(Convert.ToUInt64(ActiveOneLine.Document.DocumentElement.Attributes["BaseElement.TEID"].Value));
            int Errors, Warnings;
            MM_Oneline_Export OLExport = Model.LocateExport(ActiveOneLine);
            String ValidationResults = MM_OneLine_Validation.ValidateTopology(ActiveOneLine.Document.DocumentElement, Model,OLExport, out Errors, out Warnings);            

            //Check for a name change
            
            CIM_OneLineValidator.ApplyValidation(ValidationSummaries, ValidationResults, BaseElement.Name);
            CIM_OneLineValidator.CheckOneLineName(ActiveOneLine, BaseElement, ValidationSummaries, Repository);

            DialogResult Resp = MessageBox.Show("Would you like to (yes) update the one-line to include the model, (no) assign the model, or (cancel)?\n" + ValidationResults, "Errors: " + Errors.ToString("#,##0") + ", Warnings: " + Warnings.ToString("#,##0"), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information, MessageBoxDefaultButton.Button3);
            if (Resp == DialogResult.Yes && Array.IndexOf(ActiveOneLine.Models, Model) == -1)
                using (OracleCommand oCmd = new OracleCommand("UPDATE MM_DATABASE_ONELINE SET MODELS = MODELS || '" + Model.ID.ToString() + ",' WHERE ID=" + ActiveOneLine.ID.ToString(), Model.Db.Database as OracleConnection))
                    oCmd.ExecuteNonQuery();
            else if (Resp == DialogResult.No && this.ParentForm is frmLocateOneLine)            
                CIM_OneLineValidator.AssignOneLine(ActiveOneLine, Model, BaseElement, ValidationResults, Errors, Warnings, Repository.Db, new Dictionary<String,List<string>>());            

                
                
            
            
        }

        /// <summary>
        /// Hit-test a node against a position. If it matches, return true and highlight it.
        /// </summary>
        /// <param name="Node"></param>
        /// <param name="Location"></param>
        /// <param name="Buttons"></param>
        /// <returns></returns>
        private bool HitTest(TreeNode Node, Point Location, MouseButtons Buttons)
        {
            if (Node.Tag is KeyValuePair<XmlElement, Rectangle>)
            {
                KeyValuePair<XmlElement, Rectangle> kvp = (KeyValuePair<XmlElement, Rectangle>)Node.Tag;
                if (kvp.Value.Contains(Location) && (Node.Parent.Tag is KeyValuePair<XmlElement, Rectangle> == false))
                {                   
                    Node.EnsureVisible();
                    tvOneLine.SelectedNode = Node;
                    if (Buttons == MouseButtons.Right)
                        tvOneLine_NodeMouseClick(picImage, new TreeNodeMouseClickEventArgs(Node, Buttons, 0, Location.X, Location.Y));
                    return true;
                }
            }
            foreach (TreeNode SubNode in Node.Nodes)
                if (HitTest(SubNode, Location,Buttons))
                    return true;
            return false;
        }

        /// <summary>
        /// This class handles name/voltage pairs
        /// </summary>
        private class NameVoltage : IComparable
        {
            public float Voltage { get; set; }
            public int Number { get; set; }
            public String Name { get; set; }
            public CIM_Element NodeGroup { get; set; }
            public Color Color { get; set; }
            public NameVoltage(String Number, String Name, String Voltage, CIM_Element NodeGroup, ref int RightMost, Font DrawFont)
            {
                this.Name = MM_Type_Converter.TitleCase(Name);
                this.Number = int.Parse(Number);
                this.Voltage = float.Parse(Voltage.Split(' ')[0]);
                this.NodeGroup = NodeGroup;
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                    RightMost = Math.Max(RightMost, (int)Math.Ceiling(g.MeasureString(this.ToString()+" ", DrawFont).Width));
            }

            public int CompareTo(object obj)
            {
                int Comp = -Voltage.CompareTo((obj as NameVoltage).Voltage);
                if (Comp != 0)
                    return Comp;
                else
                    return -Number.CompareTo((obj as NameVoltage).Number);
            }

            /// <summary>
            /// Report an easy-to-read identifier for our node group
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return Number.ToString() + " (" + Name + ")";
            }

        }

        /// <summary>
        /// Handle the user's clicking of a menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmsMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            cmsMenu.Close();
            if (e.ClickedItem.Text.Contains(":"))
            {
                Clipboard.SetText(e.ClickedItem.Text.Substring(e.ClickedItem.Text.IndexOf(':') + 1).Trim());
                return;
            }

            //Make sure our model is opened
            if (e.ClickedItem.Text == "Copy &image to clipboard" || e.ClickedItem.Text == "Copy xml to &clipboard")
            { }
            else
            {
                if (ActiveModel.State == MM_Database_Model.enumModelState.Unopened)
                    if (MessageBox.Show("Open model " + ActiveModel.Name + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                        return;
                    else
                        ActiveModel.InitiateLoading();
                while (ActiveModel.State != MM_Database_Model.enumModelState.Completed && ActiveModel.State != MM_Database_Model.enumModelState.Unopened)
                    if (ActiveModel.State == MM_Database_Model.enumModelState.Error)
                        return;
                    else
                    {
                        Thread.Sleep(500);
                        Application.DoEvents();
                    }
                if (ActiveModel.State == MM_Database_Model.enumModelState.Unopened)
                    return;
            }

            if (e.ClickedItem.Text == "&Update one-line layout" && MessageBox.Show("Are you sure you would like to update this one-line layout? Doing so will make all prior one-lines for this substation not be checked during future topology validations.", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                ActiveOneLine = new MM_Database_OneLine(ActiveOneLine);
                ActiveOneLine.LayoutVersion++;
                CIM_Element BaseElement = ActiveModel.FromRdfID(ActiveOneLine.rdfID);
                if (ActiveOneLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.BreakerToBreaker)
                    foreach (CIM_Trace Trace in ActiveModel.Traces.Values)
                        if (Trace.rdfID.Equals(ActiveOneLine.rdfID))
                        {
                            BaseElement = Trace;
                            break;
                        }

                //Create a new form
                frmOneLineEditor olEditor = new frmOneLineEditor(ActiveOneLine, ActiveModel, BaseElement, Repository);

                //Try and find our owner form
                WeifenLuo.WinFormsUI.Docking.DockPanel TargetPanel = null;
                Form FormToCheck = this.ParentForm;
                do
                {
                    foreach (Control ctl in FormToCheck.Controls)
                        if (ctl is WeifenLuo.WinFormsUI.Docking.DockPanel)
                            TargetPanel = ctl as WeifenLuo.WinFormsUI.Docking.DockPanel;
                    FormToCheck = FormToCheck.ParentForm;
                } while (TargetPanel == null && FormToCheck != null);

                if (TargetPanel != null)
                    olEditor.Show(TargetPanel);
                else
                    olEditor.Show();
            }

            else if (e.ClickedItem.Text == "&Export JPEG image" || e.ClickedItem.Text.Replace("&", "").StartsWith("Export PNG image") || e.ClickedItem.Text == "Export &NETMOM WebFG display" || e.ClickedItem.Text == "Export &OTS WebFG display" || e.ClickedItem.Text == "Export &SCADA WebFG display" || e.ClickedItem.Text == "Export &XML display")
                using (SaveFileDialog sFd = new SaveFileDialog())
                {
                    sFd.Title = e.ClickedItem.Text;
                    sFd.Filter = e.ClickedItem.Tag as string;
                    if (e.ClickedItem.Text.Contains("SCADA"))
                        sFd.FileName = "SCADA-" + ActiveOneLine.Names[0] + Path.GetExtension(sFd.Filter.Split('|')[1]);
                    else if (e.ClickedItem.Text.Contains("NETMOM"))
                        sFd.FileName = "NETMOM-" + ActiveOneLine.Names[0] + Path.GetExtension(sFd.Filter.Split('|')[1]);
                    else if (e.ClickedItem.Text.Contains("OTS"))
                        sFd.FileName = "OTS-" + ActiveOneLine.Names[0] + Path.GetExtension(sFd.Filter.Split('|')[1]);
                    else
                        sFd.FileName = ActiveOneLine.Names[0] + Path.GetExtension(sFd.Filter.Split('|')[1]);
                    if (sFd.ShowDialog() == DialogResult.Cancel)
                        return;
                    else if (Path.GetExtension(sFd.FileName).Equals(".jpg", StringComparison.CurrentCultureIgnoreCase))
                    {
                        MM_Image_Exporter.ExportImage(ActiveOneLine, ActiveModel, sFd.FileName, ImageFormat.Jpeg, ActiveModel.LocateExport(ActiveOneLine), Repository.Db.GetLastAssignment(ActiveOneLine));
                        System.Diagnostics.Process.Start(sFd.FileName);
                    }
                    else if (Path.GetExtension(sFd.FileName).Equals(".png", StringComparison.CurrentCultureIgnoreCase))
                    {

                        //Make our presentation options as needed
                        if (e.ClickedItem.Text.Contains("thickened lines"))
                            MM_OneLine_Element.ThicknessOverride = 3f;

                        if (e.ClickedItem.Text.Contains("white background"))
                            MM_OneLine_Element.WhiteBackground = true;

                        if (e.ClickedItem.OwnerItem != null && e.ClickedItem.OwnerItem.Text == "&Export node group images")
                        {
                            //Start by determining our node groups
                            SortedDictionary<NameVoltage, String> NamedVoltages = new SortedDictionary<NameVoltage, string>();
                            CIM_Element Substation;
                            CIM_Element[] NodeGroups;

                            if (ActiveOneLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.Substation)
                            {
                                Substation = ActiveModel.FromRdfID(ActiveOneLine.rdfID);
                                NodeGroups = Substation.ElementsOfType("etx:ConnectivityNodeGroup");
                            }
                            else
                            {
                                Substation = ActiveModel.Traces[ActiveOneLine.Names[0]];
                                List<CIM_Element> Nodes = new List<CIM_Element>();
                                foreach (CIM_Element Elem in Substation.Elements)
                                    if (Elem.TypeName == "cim:ConnectivityNode")
                                        foreach (CIM_Element NodeGroup in Elem.Parse("etx:ConnectivityNodeGroup", ActiveModel))
                                            if (!Nodes.Contains(NodeGroup))
                                                Nodes.Add(NodeGroup);                                        
                                NodeGroups = Nodes.ToArray();
                            }

                            
                            MM_Color_Rotator ColorRotator = new MM_Color_Rotator(true);

                            //First, assign our node groups and determine how far they will go
                            int DeltaX = 0;
                            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                                DeltaX = (int)Math.Ceiling(g.MeasureString("Connectivity Node Groups: ", this.Font).Width);


                            

                            foreach (CIM_Element NodeGroup in NodeGroups)
                            {
                                NameVoltage nV = new NameVoltage(NodeGroup["etx:ConnectivityNodeGroup.PSSEBusNumber"], NodeGroup["etx:ConnectivityNodeGroup.PSSEBusName"], NodeGroup.VoltageLevel.Name, NodeGroup, ref DeltaX, this.Font);
                                NamedVoltages.Add(nV, nV.ToString());
                            }
                            DeltaX += 15;


                            Bitmap OutBaseImage = MM_Image_Exporter.OneLineImage(ActiveOneLine, ActiveModel, ActiveModel.LocateExport(ActiveOneLine), Repository.Db.GetLastAssignment(ActiveOneLine));
                            using (Bitmap OutImage = new Bitmap(OutBaseImage.Width + DeltaX, OutBaseImage.Height, PixelFormat.Format32bppArgb))
                            using (Graphics g = Graphics.FromImage(OutImage))
                            {
                                //Clear our background and draw our one-line
                                if (MM_OneLine_Element.WhiteBackground)
                                    g.Clear(Color.White);
                                else
                                    g.Clear(Color.Black);
                                g.DrawImage(OutBaseImage, DeltaX, 0);
                                OutBaseImage.Dispose();
                                OutBaseImage = null;

                                //Now, highlight our nodes and write out our keys
                                int CurY = 20;
                                float LastVoltage = 0;
                                g.DrawString("Connectivity Node Groups:", this.Font, MM_OneLine_Element.WhiteBackground ? Brushes.Black : Brushes.White, 10, CurY += this.Font.Height + 5);
                                XmlNode NodeElem;
                                foreach (NameVoltage nv in NamedVoltages.Keys)
                                {
                                    nv.Color = ColorRotator.NextColor();
                                    using (Pen DrawPen = new Pen(nv.Color, 2))
                                        foreach (CIM_Link Node in nv.NodeGroup.Links)
                                            if (Node.TypeName == "cim:ConnectivityNode" && (NodeElem = ActiveOneLine.Document.SelectSingleNode("/One_Line/Nodes/Node[@rdfID='" + Node.rdfID + "']")) != null)
                                            {                                                
                                                Rectangle NodeRect = MM_Serializable<Rectangle>.ConvertObject(NodeElem.Attributes["Bounds"].Value, null);
                                                NodeRect.X += DeltaX - 2;
                                                NodeRect.Y -= 2;
                                                NodeRect.Width += 4;
                                                NodeRect.Height += 4;
                                                /*using (StringFormat f = new StringFormat())
                                                {
                                                    f.Alignment = f.LineAlignment = StringAlignment.Center;
                                                    g.DrawString(nv.Number.ToString(), this.Font, Brushes.White, NodeRect.X + (NodeRect.Width / 2), NodeRect.Y + (NodeRect.Height / 2), f);
                                                }*/
                                                g.DrawRectangle(DrawPen, NodeRect);
                                            }
                                    if (LastVoltage != nv.Voltage)
                                        using (Font CapFont = new Font(this.Font, FontStyle.Underline | FontStyle.Bold))
                                            g.DrawString((LastVoltage = nv.Voltage).ToString() + " KV", CapFont, Brushes.White, 10, CurY += this.Font.Height + 5);
                                    using (SolidBrush sB = new SolidBrush(nv.Color))
                                        g.DrawString(nv.ToString(), this.Font, sB, 10, CurY += this.Font.Height + 5);
                                }
                                OutImage.Save(sFd.FileName, ImageFormat.Png);
                                OutImage.Dispose();
                            }                          
                        }
                        else
                            using (Bitmap OutImage = MM_Image_Exporter.OneLineImage(ActiveOneLine, ActiveModel, ActiveModel.LocateExport(ActiveOneLine), Repository.Db.GetLastAssignment(ActiveOneLine)))
                                OutImage.Save(sFd.FileName, ImageFormat.Png);

                        MM_OneLine_Element.WhiteBackground = false;
                        MM_OneLine_Element.ThicknessOverride = 1f;

                        System.Diagnostics.Process.Start(sFd.FileName);
                    }
                    else if (Path.GetExtension(sFd.FileName).Equals(".ddl", StringComparison.CurrentCultureIgnoreCase))
                        MM_DDL_Exporter.ExportOneLine(ActiveOneLine, ActiveModel, sFd.FileName, e.ClickedItem.Text.Contains("SCADA"), e.ClickedItem.Text.Contains("OTS"), picImage.Image, ActiveModel.LocateExport(ActiveOneLine), null, Repository.Db.GetLastAssignment(ActiveOneLine));
                    else if (Path.GetExtension(sFd.FileName).Equals(".MM_OneLine", StringComparison.CurrentCultureIgnoreCase))
                        try
                        {
                            ActiveOneLine.WriteOneLine(sFd.FileName, ActiveModel);
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Error exporting one-line:\n\n" + ex.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }

                }
            else if (e.ClickedItem.Text == "E&xport and compile WebFG displays")
                using (FolderBrowserDialog fB = new FolderBrowserDialog())
                {
                    fB.Description = "Please select the folder for exported DDLs and compiled one-lines";
                    if (fB.ShowDialog() != DialogResult.OK)
                        return;

                    //Determine our target directory, and ensure that our structure is in place
                    XmlElement xExport = Repository.xConfig.SelectSingleNode("Configuration/OneLineExport") as XmlElement;
                    Dictionary<String, Object> OutDic = new Dictionary<string, object>();
                    OutDic.Add("Model", ActiveModel);
                    OutDic.Add("PriorModel", ActiveModel.PriorModel);
                    CIM_OneLineValidator OLValidator = new CIM_OneLineValidator(xExport, OutDic, Repository);
                    String ImageDirectory = OLValidator.ProcessString(xExport.Attributes["OLImageDirectory"].Value);
                    String EMSDirectory = OLValidator.ProcessString(xExport.Attributes["EMSDirectory"].Value);
                    Exception CompilationException = MM_DDL_Exporter.WriteOneLine(ActiveOneLine, ActiveModel, fB.SelectedPath, fB.SelectedPath, ImageDirectory, EMSDirectory, ActiveOneLine.Names[0], "", picImage.Image, Repository, true, true, true, true, null, null, Repository.Db.GetLastAssignment(ActiveOneLine));
                    if (CompilationException != null)
                        MessageBox.Show("One-line compilation failed: " + MM_System_Interface.ConvertError(CompilationException), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    else
                        MessageBox.Show("One-line compilation completed.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            else if (e.ClickedItem.Text == "&Edit one-line")
            {

                CIM_Element BaseElement = ActiveModel.FromRdfID(ActiveOneLine.rdfID);
                if (ActiveOneLine.OneLineType == MM_Database_OneLine.OneLineTypeEnum.BreakerToBreaker)
                    foreach (CIM_Trace Trace in ActiveModel.Traces.Values)
                        if (Trace.rdfID.Equals(ActiveOneLine.rdfID))
                        {
                            BaseElement = Trace;
                            break;
                        }

                //Create a new form
                frmOneLineEditor olEditor = new frmOneLineEditor(ActiveOneLine, ActiveModel, BaseElement, Repository);

                //Try and find our owner form
                WeifenLuo.WinFormsUI.Docking.DockPanel TargetPanel = null;
                Form FormToCheck = this.ParentForm;
                do
                {
                    foreach (Control ctl in FormToCheck.Controls)
                        if (ctl is WeifenLuo.WinFormsUI.Docking.DockPanel)
                            TargetPanel = ctl as WeifenLuo.WinFormsUI.Docking.DockPanel;
                    FormToCheck = FormToCheck.ParentForm;
                } while (TargetPanel == null && FormToCheck != null);

                if (TargetPanel != null)
                    olEditor.Show(TargetPanel);
                else
                    olEditor.Show();
            }
            else if (e.ClickedItem.Text == "Copy &image to clipboard")
            {
                Clipboard.Clear();
                Clipboard.SetImage(picImage.Image);
            }
            else if (e.ClickedItem.Text == "Copy xml to &clipboard")
            {
                Clipboard.Clear();
                using (MemoryStream mS = new MemoryStream())
                using (XmlTextWriter xW = new XmlTextWriter(mS, new UTF8Encoding(false)))
                {
                    xW.Formatting = Formatting.Indented;
                    ActiveOneLine.Document.WriteTo(xW);
                    xW.Flush();
                    Clipboard.Clear();
                    Clipboard.SetText(new UTF8Encoding(false).GetString(mS.ToArray()), TextDataFormat.UnicodeText);
                }
            }
        }   
        

        #region Mouse handling in the picture area
        /// <summary>
        /// Handle the mouse down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void picImage_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)            
                MouseDownPoint = e.Location;
            SelectedElement = null;
            foreach (KeyValuePair<XmlElement, Rectangle> kvp in ElementXml)
                if (kvp.Value.Contains(e.Location))
                {
                    if (e.Button == MouseButtons.Left)
                        SelectedElement = kvp.Key;
                    tvOneLine.SelectedNode = Elements[new CIM_RdfID(kvp.Key.Attributes["rdfID"].Value)];
                    return;
                }                                                         
        }
        
        /// <summary>
        /// Handle mouse movement
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void picImage_MouseMove(object sender, MouseEventArgs e)
        {
            Rectangle FoundRect;
            if (EnableDragDrop && e.Button == MouseButtons.Left && SelectedElement != null && ElementXml.TryGetValue(SelectedElement, out FoundRect) && FoundRect.Contains(e.Location))
                picImage.DoDragDrop(SelectedElement, DragDropEffects.All);
        }

        /// <summary>
        /// Handle the mouse up
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void picImage_MouseUp(object sender, MouseEventArgs e)
        {
            SelectedElement = null;
        }

        /// <summary>
        /// When the user clicks the mouse on the image, try and locate everything
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void picImage_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
                foreach (TreeNode tn in tvOneLine.Nodes)
                    if (HitTest(tn, e.Location, e.Button))
                        return;

            //If we haven't found a node to interact with, show our options
            if (e.Button == MouseButtons.Right)
            {
                cmsMenu.Items.Clear();
                ActiveOneLine.PropigateMenu((cmsMenu.Items.Add("One-Line Information") as ToolStripMenuItem).DropDownItems);
                cmsMenu.Items.Add("-");

                cmsMenu.Items.Add("&Export JPEG image").Tag = "JPEG Image (*.jpg)|*.jpg";
                cmsMenu.Items.Add("&Export PNG image").Tag = "PNG Image (*.png)|*.png";
                ToolStripMenuItem SubPres = cmsMenu.Items.Add("&Export PNG presentation image") as ToolStripMenuItem;
                SubPres.DropDownItems.Add("Export PNG image with thickened lines").Tag = "PNG Image (*.png)|*.png";
                SubPres.DropDownItems.Add("Export PNG image with thickened lines and white background").Tag = "PNG Image (*.png)|*.png";
                SubPres.DropDownItemClicked += cmsMenu_ItemClicked;

                SubPres = cmsMenu.Items.Add("&Export node group images") as ToolStripMenuItem;
                SubPres.DropDownItems.Add("&Export PNG image").Tag = "PNG Image (*.png)|*.png";                
                SubPres.DropDownItems.Add("Export PNG image with thickened lines").Tag = "PNG Image (*.png)|*.png";
                SubPres.DropDownItems.Add("Export PNG image with thickened lines and white background").Tag = "PNG Image (*.png)|*.png";
                SubPres.DropDownItemClicked += cmsMenu_ItemClicked;
                

                cmsMenu.Items.Add("Copy &image to clipboard");
                cmsMenu.Items.Add("-");
                cmsMenu.Items.Add("Export &NETMOM WebFG display").Tag = "WebFG display (*_dset.ddl)|*_dset.ddl";
                cmsMenu.Items.Add("Export &OTS WebFG display").Tag = "WebFG display (*_dset.ddl)|*_dset.ddl";
                cmsMenu.Items.Add("Export &SCADA WebFG display").Tag = "WebFG display (*_dset.ddl)|*_dset.ddl";
                cmsMenu.Items.Add("E&xport and compile WebFG displays").Tag = "Path for DDLs and FGDisplays|dummy.ddl_exp";
                cmsMenu.Items.Add("-");

                if (ActiveModel.Repository.GetType().GetField("Model_Collection") != null)
                    MM_Database_Model.AddModelMenus((cmsMenu.Items.Add("Validate one-line against") as ToolStripMenuItem).DropDownItems, ActiveModel.Repository.Model_Collection.Values, mnuModels_DropDownItemClicked);

                cmsMenu.Items.Add("-");
                if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
                    cmsMenu.Items.Add("&Edit one-line");
                cmsMenu.Items.Add("&Update one-line layout");
                cmsMenu.Items.Add("-");

                cmsMenu.Items.Add("Export &XML display").Tag = "Macomber Map XML Display (*.MM_OneLine)|*.MM_OneLine";
                cmsMenu.Items.Add("Copy xml to &clipboard");
                cmsMenu.Show(picImage, e.Location);
            }
        }
        #endregion

    }
}
